package hikari.gui.assets;

import com.gooddies.swing.hFileChooser;
import com.gooddies.wiring.Wiring;
import com.gooddies.xml.XPathEngine;
import hikari.utils.FormatUtils;
import hikari.utils.Utils;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author sad
 */
public class AssetsImporterSelector extends javax.swing.JDialog {

    private static final String ASSETS_URL = "https://dl.dropboxusercontent.com/u/8773223/assets/";
    private File currentAssetFile = null;
    private String currentAssetUrl = null;
    private boolean inDownloading = false;

    public AssetsImporterSelector(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        descriptionField.setContentType("text/html");
        categoriesTree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                categorySelected(e.getPath());
            }
        });
        setLocationRelativeTo(null);
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();
        jButton1 = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        updateFromServerButton = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        categoriesTree = new javax.swing.JTree();
        jScrollPane2 = new javax.swing.JScrollPane();
        descriptionField = new javax.swing.JEditorPane();
        fromDiskButton = new javax.swing.JButton();
        installButton = new javax.swing.JButton();
        closeButton = new javax.swing.JButton();
        workProgressBar = new javax.swing.JProgressBar();

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 0, Short.MAX_VALUE)
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 0, Short.MAX_VALUE)
        );

        jButton1.setText("jButton1");

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("Import Asset");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        jLabel1.setBackground(new java.awt.Color(255, 255, 255));
        jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        jLabel1.setText("Here you can download asset from server, or load it from disk.");
        jLabel1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 10, 1, 1));
        jLabel1.setOpaque(true);

        updateFromServerButton.setText("Update from server");
        updateFromServerButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateFromServerButtonActionPerformed(evt);
            }
        });

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Root");
        categoriesTree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        jScrollPane1.setViewportView(categoriesTree);

        descriptionField.setEditable(false);
        jScrollPane2.setViewportView(descriptionField);

        fromDiskButton.setText("From Disk");
        fromDiskButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                fromDiskButtonActionPerformed(evt);
            }
        });

        installButton.setText("Install");
        installButton.setEnabled(false);
        installButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                installButtonActionPerformed(evt);
            }
        });

        closeButton.setText("Close");
        closeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(workProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(closeButton))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(updateFromServerButton)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(fromDiskButton))
                            .addComponent(jScrollPane1))
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(18, 18, 18)
                                .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
                            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                .addGap(204, 204, 204)
                                .addComponent(installButton)))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(7, 7, 7)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(updateFromServerButton)
                    .addComponent(installButton)
                    .addComponent(fromDiskButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 287, Short.MAX_VALUE)
                    .addComponent(jScrollPane2))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(workProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
                .addComponent(closeButton)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void updateFromServerButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateFromServerButtonActionPerformed
        updateCategoriesListFromServer();
    }//GEN-LAST:event_updateFromServerButtonActionPerformed

    private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
        close();
    }//GEN-LAST:event_closeButtonActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        close();
    }//GEN-LAST:event_formWindowClosing

    private void fromDiskButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fromDiskButtonActionPerformed
        loadFromDisk();
    }//GEN-LAST:event_fromDiskButtonActionPerformed

    private void installButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_installButtonActionPerformed
        installAsset();
    }//GEN-LAST:event_installButtonActionPerformed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTree categoriesTree;
    private javax.swing.JButton closeButton;
    private javax.swing.JEditorPane descriptionField;
    private javax.swing.JButton fromDiskButton;
    private javax.swing.JButton installButton;
    private javax.swing.JButton jButton1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JButton updateFromServerButton;
    private javax.swing.JProgressBar workProgressBar;
    // End of variables declaration//GEN-END:variables

    private void close() {
        if (!inDownloading) {
            setVisible(false);
        }
    }

    private void enableInterface(boolean enabled) {
        categoriesTree.setEnabled(enabled);
        closeButton.setEnabled(enabled);
        fromDiskButton.setEnabled(enabled);
        installButton.setEnabled(enabled);
        updateFromServerButton.setEnabled(enabled);
    }

    private void categorySelected(TreePath path) {
        if (path.getLastPathComponent() == getTreeModel().getRoot()) {
            return;
        }
        CategoryNode selectedNode = (CategoryNode) path.getLastPathComponent();
        if (selectedNode.getAllowsChildren() == true) {
            descriptionField.setText("");
            installButton.setEnabled(false);
        } else {
            updatePluginDescription(selectedNode.getUrl());
        }
    }

    private void updatePluginDescription(final String url) {
        workProgressBar.setIndeterminate(true);
        workProgressBar.setStringPainted(false);
        currentAssetFile = null;
        SwingWorker worker = new SwingWorker() {
            private String description;

            @Override
            protected Object doInBackground() throws Exception {
                try {
                    description = downloadStringFromUrl(ASSETS_URL + url + ".xml");
                } catch (Exception ex) {
                    Wiring.getComponent(Utils.class).logError(ex);
                }
                return true;
            }

            @Override
            protected void done() {
                try {
                    currentAssetUrl = url + ".hasset";
                    showDescriptionXml(description);
                    installButton.setEnabled(true);
                } finally {
                    workProgressBar.setIndeterminate(false);
                }
            }
        };
        worker.execute();
    }

    private void updateCategoriesListFromServer() {
        workProgressBar.setIndeterminate(true);
        workProgressBar.setStringPainted(false);
        final String previousText = updateFromServerButton.getText();
        updateFromServerButton.setEnabled(false);
        updateFromServerButton.setText("Try to connect");
        SwingWorker worker = new SwingWorker() {
            String xml;

            @Override
            protected Object doInBackground() throws Exception {
                xml = getXmlFromServer();
                return true;
            }

            @Override
            protected void done() {
                try {
                    updateCategoriesTree(xml);
                } finally {
                    workProgressBar.setIndeterminate(false);
                    workProgressBar.setValue(0);
                    updateFromServerButton.setEnabled(true);
                    updateFromServerButton.setText(previousText);
                }
            }
        };
        worker.execute();
    }

    private void updateCategoriesTree(String xml) {
        if (xml == null || xml.trim().isEmpty()) {
            return;
        }

        getRoot().removeAllChildren();
        XPathEngine xpath = new XPathEngine(xml.trim());
        NodeList nodes = xpath.getNodeSetByXPath("/xml/category");
        addNodeList(getRoot(), nodes, xpath);
        categoriesTree.expandPath(new TreePath(getTreeModel().getPathToRoot(getRoot())));
    }

    private void addNodeList(DefaultMutableTreeNode root, NodeList nodes, XPathEngine xpath) {
        int count = nodes.getLength();
        for (int i = 0; i < count; i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                CategoryNode category = new CategoryNode();
                switch (element.getTagName()) {
                    case "category":
                        category.setUserObject(element.getAttribute("name"));
                        category.setAllowsChildren(true);
                        root.add(category);
                        NodeList childNodes = xpath.getNodeSetByXPath("./*", element);
                        addNodeList(category, childNodes, xpath);
                        break;
                    case "asset":
                        category.setUserObject(element.getAttribute("title"));
                        category.setUrl(element.getAttribute("path"));
                        category.setAllowsChildren(false);
                        root.add(category);
                        break;
                }
            }
        }
    }

    private DefaultMutableTreeNode getRoot() {
        return (DefaultMutableTreeNode) getTreeModel().getRoot();
    }

    private DefaultTreeModel getTreeModel() {
        return (DefaultTreeModel) categoriesTree.getModel();
    }

    private String downloadStringFromUrl(String urlPath) {
        try {
            URL url = new URL(urlPath);
            ByteArrayOutputStream byteStream;
            try (BufferedInputStream stream = new BufferedInputStream(url.openStream())) {
                byteStream = new ByteArrayOutputStream();
                IOUtils.copy(stream, byteStream);
            }
            String xml = byteStream.toString("UTF-8");
            byteStream.close();
            return xml;
        } catch (Exception ex) {
            Logger.getLogger(AssetsImporterSelector.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private String getXmlFromServer() {
        return downloadStringFromUrl(ASSETS_URL + "assets.xml");
    }

    private void loadFromDisk() {
        File file = selectAssetFile();
        if (file != null) {
            showDescriptionFile(file);
        }
    }

    private File selectAssetFile() {
        hFileChooser chooser = new hFileChooser();
        chooser.setFileFilter("Asset Archive(*.hasset)|hasset");
        if (hFileChooser.APPROVE_OPTION == chooser.showOpenDialog(this)) {
            return chooser.getSelectedFile();
        } else {
            return null;
        }
    }

    private void installAsset() {
        if (currentAssetFile != null && currentAssetFile.exists()) {
            AssetsInstallationDialog installDialog = new AssetsInstallationDialog(this, true);
            installDialog.setAssetFile(currentAssetFile);
            installDialog.setVisible(true);
        } else if (currentAssetUrl != null) {
            downloadAssetFile(ASSETS_URL + currentAssetUrl);
        }
    }

    private File downloadAssetFile(String url) {
        try {
            URL urlConnector = new URL(url);
            final URLConnection connection = urlConnector.openConnection();
            connection.connect();
            final int length = connection.getContentLength();
            workProgressBar.setMaximum(length);
            workProgressBar.setValue(0);
            workProgressBar.setString("Download Asset");
            workProgressBar.setStringPainted(true);
            enableInterface(false);
            final File file = File.createTempFile("asset", "asset");
            inDownloading = true;
            SwingWorker worker = new SwingWorker() {
                @Override
                protected Object doInBackground() throws Exception {
                    try {
                        FileOutputStream outputStream;
                        try (BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream())) {
                            outputStream = new FileOutputStream(file);
                            int read = 0;
                            while (read < (length - 1)) {
                                int value = inputStream.read();
                                if (value != -1) {
                                    read++;
                                    outputStream.write(value);
                                    if ((read % 200) == 0) {
                                        showProgressBar(read);
                                    }
                                }
                            }

                            showProgressBar(read + 1);
                        }
                        outputStream.close();
                    } catch (Exception ex) {
                        Wiring.getComponent(Utils.class).logError(ex);
                        Wiring.getComponent(Utils.class).errorMessage("Error while downloading asset file. " + ex.getLocalizedMessage());
                    } finally {
                        inDownloading = false;
                    }
                    return true;
                }

                @Override
                protected void done() {
                    enableInterface(true);
                    onFileDownloaded(file);
                }
            };
            worker.execute();
        } catch (Exception ex) {
            Logger.getLogger(AssetsImporterSelector.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private void onFileDownloaded(File file) {
        currentAssetFile = file;
        AssetsInstallationDialog installDialog = new AssetsInstallationDialog(this, true);
        installDialog.setAssetFile(file);
        installDialog.setVisible(true);
    }

    private void showProgressBar(final int value) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                workProgressBar.setValue(value);
                String string = FormatUtils.convertToStringRepresentation(value) + "/" + FormatUtils.convertToStringRepresentation(workProgressBar.getMaximum());
                workProgressBar.setString(string);
            }
        });
    }

    private String extractDescriptionXml(File file) throws FileNotFoundException, IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        FileInputStream fin = new FileInputStream(file);
        BufferedInputStream bin = new BufferedInputStream(fin);
        try (ZipInputStream zin = new ZipInputStream(bin)) {
            ZipEntry ze;
            while ((ze = zin.getNextEntry()) != null) {
                if (ze.getName().equals("metadata.xml")) {
                    byte[] buffer = new byte[8192];
                    int len;
                    while ((len = zin.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }

                    out.close();
                    break;
                }
            }
        }

        if (out.size() == 0) {
            return null;
        }

        return new String(out.toByteArray(), "UTF-8");
    }

    private void showDescriptionFile(File file) {
        try {
            String descriptionXml = extractDescriptionXml(file);
            if (descriptionXml == null) {
                Wiring.getComponent(Utils.class).errorMessage(this, "It seems the asset file does not contain metadata.xml file");
                return;
            }

            showDescriptionXml(descriptionXml);
            currentAssetFile = file;
            installButton.setEnabled(true);
        } catch (IOException ex) {
            Wiring.getComponent(Utils.class).errorMessage(this, "Error while opening asset file. " + ex.getLocalizedMessage());
            
        }
    }

    private void showDescriptionXml(String xml) {
        XPathEngine xpath = new XPathEngine(xml.trim());
        String title = xpath.getTextValueByXPath("/xml/title/text()").trim();
        String author = xpath.getTextValueByXPath("/xml/author/text()").trim();
        String email = xpath.getTextValueByXPath("/xml/email/text()").trim();
        String description = xpath.getTextValueByXPath("/xml/description/text()").trim();
        String version = xpath.getTextValueByXPath("/xml/version/text()").trim();

        StringBuilder sb = new StringBuilder();
        sb.append("<html>\n");
        sb.append("<body>\n");
        sb.append("\t<h3><b>").append(title).append("</b></h3>\n");
        sb.append("\t<b>Author:</b>").append(author).append("<br/>\n");
        sb.append("\t<b>Email:</b>").append(email).append("<br/><br/>\n");
        sb.append("\t<b>Version:</b>").append(version).append("<br/><br/>\n");
        sb.append(description);
        sb.append("\n</body>\n");
        sb.append("</html>");
        descriptionField.setText(sb.toString());
    }
}